﻿using DGZImp.BasProject.DomainModels;
using DGZImp.Core.Configuration;
using DGZImp.Core.DbSqlSugar;
using DGZImp.Core.EFDbContext;
using DGZImp.Core.Exceptions;
using DGZImp.Core.Extensions;
using DGZImp.Core.Services;
using DGZImp.MdsProject.Base;
using DGZImp.MdsProject.Devices;
using DGZImp.MdsProject.Devices.Base;
using DGZImp.MdsProject.Devices.Entities;
using DGZImp.MdsProject.Devices.Helper;
using DGZImp.MdsProject.Devices.Models;
using DGZImp.MdsProject.DomainModels;
using DGZImp.MdsProject.Helper;
using DGZImp.MdsProject.Models;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nomad.Iot;
using Nomad.Iot.Profinet.Panasonic;
using Nomad.Iot.Profinet.Siemens;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace DGZImp.MdsProject
{
    public class MdsInit : ISingleton
    {
        public ISqlSugarClient Db = ServiceProviderAccessor.ServiceProvider.GetService<ServiceDbContext>()?.SqlSugarClient;
        public List<DataServe> serves = new List<DataServe>();
        public List<RealDevice> devices = new List<RealDevice>();

        public async Task Init()
        {
            if (!AppSetting.Configuration.GetValue<bool>("BizConfig:Mds:AutoStartConn"))
            {
                SysLogger.LogInformation("未开启MDS数据采集，跳过初始化");
                return;
            }
            SysLogger.LogInformation("MDS数据采集初始化开始");
            try
            {
                var dataServes = await Db.Set<Mds_DataServe>().ToListAsync();
                var dataServeConfigs = await Db.Set<Mds_DataServeConfig>().ToListAsync();
                var realDevices = await Db.Set<Mds_RealDevice>()
                    .LeftJoin<Bas_Machine>((a, b) => a.MachineId == b.MachineId && b.IsValid == 1)
                    .Select((a, b) => new
                    {
                        a.RealDeviceId,
                        a.MachineId,
                        a.DeviceName,
                        a.Category,
                        a.DataServeId,
                        a.AliasCode,
                        a.IsMasterDevice,
                        b.MachineCode
                    })
                    .ToListAsync();
                var realDeviceConfigs = await Db.Set<Mds_RealDeviceConfig>().ToListAsync();

                serves = dataServes.Select(x => new DataServe
                {
                    Id = x.DataServeId.ToString(),
                    Name = x.ServeName,
                    ServeType = (ServeTypeEmun)x.ServeType,
                    Ip = x.Ip,
                    Port = (int)x.Port,
                    BaseUrl = x.BaseUrl,
                    IsAutoRead = x.IsAutoRead == 1,
                    ReadInterval = x.ReadInterval == null ? 2000 : (int)x.ReadInterval,
                    RequestModel = x.ReadRequestData,
                    DbBlocks = dataServeConfigs.Where(j => j.DataServeId == x.DataServeId && j.DbBlock.IsValuable())
                        .Select(j => new DbBlockCfgItem { DbBlock = j.DbBlock, Length = (ushort)j.ReadLength })
                        .ToList(),
                }).ToList();

                devices = realDevices.Select(x => new RealDevice
                {
                    Id = x.RealDeviceId.ToString(),
                    MachineId = x.MachineId,
                    MachineCode = x.MachineCode,
                    Name = x.DeviceName,
                    AliasCode = x.AliasCode,
                    Type = (RealDeviceTypeEmun)x.Category,
                    IsMasterDevice = x.IsMasterDevice,
                    ServeId = x.DataServeId.ToString(),
                    PlcDataMaps = realDeviceConfigs.Where(j => j.RealDeviceId == x.RealDeviceId && j.DbBlock.IsValuable())
                        .Select(j => new PlcDataMapItem
                        {
                            PropName = j.DataProps,
                            DbBlock = j.DbBlock,
                            Offset = j.Offset == null ? (ushort)0 : (ushort)j.Offset,
                            DataType = (DataTypeEnum)j.DataType,
                            Readable = j.Readable != 0,
                            Writable = j.Writable != 0,
                            WriteAddress = j.WriteAddress
                        }).ToList()
                }).ToList();

                serves.ForEach(item =>
                {

                    try
                    {
                        CreateServeInstance(item);
                    }
                    catch (Exception ex)
                    {
                        SysLogger.LogError(ex, $"MdsInit-初始化设备{item.Name}/{item.Ip}实例异常");
                    }
                });
                SysLogger.LogInformation("MDS数据采集初始化采集服务完成，服务数量:" + serves.Count);
                devices.ForEach(item =>
                {
                    try
                    {
                        CreateDeviceInstance(item);
                    }
                    catch (Exception ex)
                    {
                        SysLogger.LogError(ex, $"MdsInit-初始化设备{item.Name}/{item.MachineCode}实例异常");
                    }
                });
                SysLogger.LogInformation("MDS数据采集初始化采集设备完成，设备数量:" + devices.Count);
            }
            catch (Exception e)
            {
                SysLogger.LogError(e, "MdsInit");
                throw;
            }
        }

        private void CreateServeInstance(DataServe item)
        {
            IIotServe serve = null;
            switch (item.ServeType)
            {
                case ServeTypeEmun.SiemensPlc:
                    serve = new SiemensServe(item.Id, item.Name, SiemensPLCS.S1200, item.Ip, item.Port, item.DbBlocks, item.IsAutoRead);
                    break;
                case ServeTypeEmun.OmronPlc:
                    serve = new OmronServe(item.Id, item.Name, item.Ip, item.Port, item.DbBlocks, item.IsAutoRead);
                    break;
                case ServeTypeEmun.MelsecPlc:
                    serve = new MelsecServe(item.Id, item.Name, item.Ip, item.Port, item.DbBlocks, item.IsAutoRead);
                    break;
                case ServeTypeEmun.Http:
                    serve = new HttpServe(item.Id, item.Name, item.BaseUrl, item.IsAutoRead, item.ReadInterval, item.RequestModel);
                    break;
                case ServeTypeEmun.Tcp:
                    serve = new TcpServe(item.Id, item.Name, item.Ip, item.Port, item.IsAutoRead);
                    break;
                case ServeTypeEmun.WebSocket:
                    serve = new WebSocketServe(item.Id, item.Name, item.BaseUrl, item.IsAutoRead, item.ReadInterval, item.RequestModel);
                    break;
                default:
                    break;
            }
            item.IotServe = serve;
        }

        private void CreateDeviceInstance(RealDevice item)
        {
            IIotServe serve = serves.Where(x => x.Id == item.ServeId).FirstOrDefault()?.IotServe;
            if (serve == null) throw new AppException($"设备{item.Name}关联的数采服务未初始化");
            BaseDevice device = null;
            switch (item.Type)
            {
                case RealDeviceTypeEmun.Conveyor:
                    device = new Conveyor(item.Id, item.Name, serve, item.PlcDataMaps);
                    break;
                case RealDeviceTypeEmun.Scanner:
                    device = new Scanner(item.Id, item.Name, serve);
                    break;
                case RealDeviceTypeEmun.Hoister:
                    device = new Hoister(item.Id, item.Name, serve, item.PlcDataMaps);
                    break;
                case RealDeviceTypeEmun.RGV:
                    device = new Rgv(item.Id, item.Name, serve, item.PlcDataMaps);
                    break;
                case RealDeviceTypeEmun.AGV_ZY:
                case RealDeviceTypeEmun.AGV_DG:
                case RealDeviceTypeEmun.AGV_CB:
                case RealDeviceTypeEmun.AGV_MF:
                    device = new LsAgv(item.Id, item.Name, serve, item.Type, item.AliasCode);
                    break;
                case RealDeviceTypeEmun.Connector_TOP:
                case RealDeviceTypeEmun.Connector_BOT:
                    device = new Connector(item.Id, item.Name, serve, item.PlcDataMaps);
                    break;
                case RealDeviceTypeEmun.PRD_HF:
                case RealDeviceTypeEmun.PRD_PF:
                case RealDeviceTypeEmun.PRD_SJ:
                case RealDeviceTypeEmun.PRD_YX:
                case RealDeviceTypeEmun.PRD_ZF:
                    device = new Productor(item.Id, item.Name, serve, item.Type, item.MachineCode);
                    break;
                default:
                    break;
            }
            item.Device = device;
        }

        public async Task RecreateServe(string id)
        {
            var dataServe = await Db.DoGetByIdAsync<Mds_DataServe>(id);
            if (dataServe == null) throw new AppException($"数采服务{id}不存在");
            var serveConfigs = await Db.Set<Mds_DataServeConfig>()
                .Where(x => SqlFunc.ToString(x.DataServeId) == id).ToListAsync();
            var item = serves.Where(x => x.Id == id.ToString()).FirstOrDefault();
            if (item != null && item.IotServe != null)
            {
                item.IotServe.Dispose();
                item.IotServe = null;
                serves.Remove(item);
            }
            var deviceIds = devices.Where(x => x.ServeId == id.ToString()).Select(x => x.Id).ToList();
            devices.RemoveAll(x => deviceIds.Contains(x.Id));
            item = new DataServe
            {
                Id = dataServe.DataServeId.ToString(),
                Name = dataServe.ServeName,
                ServeType = (ServeTypeEmun)dataServe.ServeType,
                Ip = dataServe.Ip,
                Port = (int)dataServe.Port,
                BaseUrl = dataServe.BaseUrl,
                IsAutoRead = dataServe.IsAutoRead == 1,
                ReadInterval = dataServe.ReadInterval == null ? 2000 : (int)dataServe.ReadInterval,
                RequestModel = dataServe.ReadRequestData,
                DbBlocks = serveConfigs.Where(j => j.DbBlock.IsValuable())
                        .Select(j => new DbBlockCfgItem { DbBlock = j.DbBlock, Length = (ushort)j.ReadLength })
                        .ToList(),
            };
            serves.Add(item);
            CreateServeInstance(item);
            foreach (var deviceId in deviceIds)
            {
                await RecreateDevice(deviceId);
            }
        }

        public async Task RecreateDevice(string id)
        {
            var realDevice = await Db.DoGetByIdAsync<Mds_RealDevice>(id);
            if (realDevice == null) throw new AppException($"数采设备 {id} 不存在");
            var deviceInfo = await Db.DoGetByIdAsync<Bas_Machine>(realDevice.MachineId);
            if (deviceInfo == null) throw new AppException($"设备基础数据 {id} 不存在");
            var realDeviceConfig = await Db.Set<Mds_RealDeviceConfig>()
                .Where(x => SqlFunc.ToString(x.RealDeviceId) == id).ToListAsync();
            var item = devices.Where(x => x.Id == id.ToString()).FirstOrDefault();
            if (item != null && item.Device != null)
            {
                item.Device = null;
                devices.Remove(item);
            }
            item = new RealDevice
            {
                Id = realDevice.RealDeviceId.ToString(),
                MachineId = realDevice.MachineId,
                MachineCode = deviceInfo.MachineCode,
                Name = realDevice.DeviceName,
                Type = (RealDeviceTypeEmun)realDevice.Category,
                ServeId = realDevice.DataServeId.ToString(),
                PlcDataMaps = realDeviceConfig.Where(j => j.DbBlock.IsValuable())
                        .Select(j => new PlcDataMapItem
                        {
                            PropName = j.DataProps,
                            DbBlock = j.DbBlock,
                            Offset = j.Offset == null ? (ushort)0 : (ushort)j.Offset,
                            DataType = (DataTypeEnum)j.DataType,
                            Readable = j.Readable != 0,
                            Writable = j.Writable != 0,
                            WriteAddress = j.WriteAddress
                        }).ToList()
            };
            devices.Add(item);
            CreateDeviceInstance(item);
        }

        public async Task<ResMdsDeviceData> GetDeviceData(long? RealDeviceId)
        {
            var res = new ResMdsDeviceData();
            res.Device = await Db.DoGetByIdAsync<Mds_RealDevice>(RealDeviceId);
            if (res.Device == null) return res;
            var realDevice = devices.Where(x => x.Id == RealDeviceId.ToString()).FirstOrDefault();
            if (realDevice == null) return res;
            if (realDevice.Device == null) return res;

            switch (realDevice.Type)
            {
                case RealDeviceTypeEmun.Conveyor:
                    res.Data = ((Conveyor)realDevice.Device).Data.ToPropValueList();
                    res.Actions = ActionHelper.GetActions(typeof(Conveyor));
                    break;
                case RealDeviceTypeEmun.Scanner:
                    res.Data = ((Scanner)realDevice.Device).Data.ToPropValueList();
                    res.Actions = ActionHelper.GetActions(typeof(Scanner));
                    break;
                case RealDeviceTypeEmun.RGV:
                    res.Data = ((Rgv)realDevice.Device).Data.ToPropValueList();
                    res.Actions = ActionHelper.GetActions(typeof(Rgv));
                    break;
                case RealDeviceTypeEmun.AGV_ZY:
                case RealDeviceTypeEmun.AGV_CB:
                case RealDeviceTypeEmun.AGV_DG:
                case RealDeviceTypeEmun.AGV_MF:
                    res.Data = ((LsAgv)realDevice.Device).Data.ToPropValueList();
                    res.Actions = ActionHelper.GetActions(typeof(LsAgv));
                    break;
                case RealDeviceTypeEmun.Hoister:
                    res.Data = ((Hoister)realDevice.Device).Data.ToPropValueList();
                    res.Actions = ActionHelper.GetActions(typeof(Hoister));
                    break;
                case RealDeviceTypeEmun.Connector_TOP:
                case RealDeviceTypeEmun.Connector_BOT:
                    res.Data = ((Connector)realDevice.Device).Data.ToPropValueList();
                    res.Actions = ActionHelper.GetActions(typeof(Connector));
                    break;
                case RealDeviceTypeEmun.PRD_HF:
                case RealDeviceTypeEmun.PRD_PF:
                case RealDeviceTypeEmun.PRD_SJ:
                case RealDeviceTypeEmun.PRD_YX:
                case RealDeviceTypeEmun.PRD_ZF:
                    res.Data = ((Productor)realDevice.Device).Data.ToPropValueList();
                    res.Actions = ActionHelper.GetActions(typeof(Productor));
                    break;
                default:
                    break;
            }

            return res;
        }

        public async Task<List<PropsItem>> GetDataProps(RealDeviceTypeEmun DeviceType)
        {
            var res = new List<PropsItem>();
            switch (DeviceType)
            {
                case RealDeviceTypeEmun.Conveyor:
                    res = GetClassProperties(typeof(ConveyorData));
                    break;
                case RealDeviceTypeEmun.Scanner:
                    res = GetClassProperties(typeof(ScannerData));
                    break;
                case RealDeviceTypeEmun.AGV_ZY:
                case RealDeviceTypeEmun.AGV_DG:
                case RealDeviceTypeEmun.AGV_CB:
                case RealDeviceTypeEmun.AGV_MF:
                    res = GetClassProperties(typeof(AgvData));
                    break;
                case RealDeviceTypeEmun.RGV:
                    res = GetClassProperties(typeof(RgvData));
                    break;
                case RealDeviceTypeEmun.Hoister:
                    res = GetClassProperties(typeof(HoisterData));
                    break;
                case RealDeviceTypeEmun.Connector_TOP:
                case RealDeviceTypeEmun.Connector_BOT:
                    res = GetClassProperties(typeof(ConnectorData));
                    break;
                case RealDeviceTypeEmun.PRD_HF:
                case RealDeviceTypeEmun.PRD_PF:
                case RealDeviceTypeEmun.PRD_SJ:
                case RealDeviceTypeEmun.PRD_YX:
                case RealDeviceTypeEmun.PRD_ZF:
                    res = GetClassProperties(typeof(ProductorData));
                    break;
                default:
                    break;
            }
            return res;
        }

        private List<PropsItem> GetClassProperties(Type classType)
        {
            //通过反射获取属性列表
            var properties = classType.GetProperties();

            var res = properties.Select(x => new PropsItem
            {
                Name = x.Name,
                Description = x.GetCustomAttribute<DescriptionAttribute>()?.Description ?? "",
            })
            .Where(x => x.Description != "")
            .OrderBy(x => x.Description).ToList();

            return res;
        }

        public async Task<object> ExecuteAction(ReqExecuteAction req)
        {
            try
            {
                var realDevice = devices.Where(x => x.Id == req.RealDeviceId.ToString()).FirstOrDefault();
                if (realDevice == null) return null;
                return await ActionHelper.ExecuteAction(realDevice.Device, req.MethodName, req.Arguments.ToArray());
            }
            catch (Exception e)
            {
                SysLogger.LogError(e, e.Message);
                throw;
            }
        }

        /// <summary>
        /// 派发任务指令到设备，
        /// </summary>
        /// <param name="task"></param>
        /// <returns></returns>
        public async Task SendLogisticTaskToDevice(Mds_LogisticTaskDetail task)
        {
            var realDevice = await GetRealDeviceByMachineId(task.MachineId);
            OperateResult r = null;
            switch (realDevice.Type)
            {
                case RealDeviceTypeEmun.Hoister:
                    r = await ((Hoister)realDevice.Device).SendNormalTaskAsync(
                        (int)task.TaskNo, Convert.ToInt32(task.BeginSiteAlias), Convert.ToInt32(task.EndSiteAlias)
                    );
                    if (!r.IsSuccess) throw new AppException(r.Message);

                    break;
                case RealDeviceTypeEmun.RGV:
                    r = await ((Rgv)realDevice.Device).SendNormalTaskAsync(
                        RgvCommandEmun.取放货任务, (int)task.TaskNo, Convert.ToInt32(task.BeginSiteAlias), Convert.ToInt32(task.EndSiteAlias)
                    );
                    if (!r.IsSuccess) throw new AppException(r.Message);

                    break;
                case RealDeviceTypeEmun.AGV_ZY:
                case RealDeviceTypeEmun.AGV_CB:
                case RealDeviceTypeEmun.AGV_DG:
                case RealDeviceTypeEmun.AGV_MF:
                    r = await ((LsAgv)realDevice.Device).SendNormalTaskAsync(AgvCommandEmun.取放货任务, (int)task.TaskNo, task.BeginSiteAlias, task.EndSiteAlias);
                    if (!r.IsSuccess) throw new AppException(r.Message);
                    break;
                default:
                    throw new AppException($"设备类型[{realDevice.Type}]不支持直接下发物流任务，请检查");
            }
        }

        /// <summary>
        /// 派发任务指令到设备，
        /// 因需要提升物流运转效率，可同同时下发任务
        /// 下发任务当前任务时，取任务的下一序任务，依据设备类型进行分类下发：
        /// 1. 当前任务为提升机时，下序若是AGV则直接下发取放货，下序若是RGV则下发移动任务
        /// 2. 当前任务为AGV，后续为 提升机 或 RGV，则下发 移动至 下序任务的起点
        /// 3. 当前任务为RGV，后续为 提升机 则下发 移动至 下序任务的起点，若 AGV 则直接下发取放货任务
        /// </summary>
        /// <param name="preTask"></param>
        /// <returns></returns>
        public async Task<bool> SendLogisticTaskToDevice(RealDevice realDevice, int preTaskNo, Mds_LogisticTaskDetail? nextTask)
        {
            var nextDevice = await GetRealDeviceByMachineId(nextTask.MachineId);
            OperateResult r = null;
            switch (realDevice.Type)
            {
                case RealDeviceTypeEmun.Hoister:
                    if (nextDevice.Type == RealDeviceTypeEmun.RGV)
                    {
                        r = await ((Rgv)nextDevice.Device).SendNormalTaskAsync(
                              RgvCommandEmun.移动任务, preTaskNo, Convert.ToInt32(nextTask.BeginSiteAlias));
                        if (!r.IsSuccess) throw new AppException(r.Message);
                    }
                    if (nextDevice.Type == RealDeviceTypeEmun.AGV_ZY)
                    {
                        r = await ((LsAgv)nextDevice.Device).SendNormalTaskAsync(AgvCommandEmun.取放货任务,
                            (int)nextTask.TaskNo, nextTask.BeginSiteAlias, nextTask.EndSiteAlias);
                        if (!r.IsSuccess) throw new AppException(r.Message);
                        return true;
                    }
                    break;
                case RealDeviceTypeEmun.RGV:
                    if (nextDevice.Type == RealDeviceTypeEmun.Hoister)
                    {
                        r = await ((Hoister)nextDevice.Device).SendNormalTaskAsync(preTaskNo, Convert.ToInt32(nextTask.BeginSiteAlias),
                              Convert.ToInt32(nextTask.BeginSiteAlias));
                        if (!r.IsSuccess) throw new AppException(r.Message);
                    }
                    if (nextDevice.Type == RealDeviceTypeEmun.AGV_DG)
                    {
                        r = await ((LsAgv)nextDevice.Device).SendNormalTaskAsync(AgvCommandEmun.取放货任务,
                            (int)nextTask.TaskNo, nextTask.BeginSiteAlias, nextTask.EndSiteAlias);
                        if (!r.IsSuccess) throw new AppException(r.Message);
                        return true;
                    }
                    break;
                case RealDeviceTypeEmun.AGV_ZY:
                    if (nextDevice.Type == RealDeviceTypeEmun.Hoister)
                    {
                        r = await ((Hoister)nextDevice.Device).SendNormalTaskAsync(preTaskNo, Convert.ToInt32(nextTask.BeginSiteAlias),
                              Convert.ToInt32(nextTask.BeginSiteAlias));
                        if (!r.IsSuccess) throw new AppException(r.Message);
                    }
                    break;
                case RealDeviceTypeEmun.AGV_DG:

                    if (nextDevice.Type == RealDeviceTypeEmun.RGV)
                    {
                        r = await ((Rgv)nextDevice.Device).SendNormalTaskAsync(
                              RgvCommandEmun.移动任务, preTaskNo, Convert.ToInt32(nextTask.BeginSiteAlias));
                        if (!r.IsSuccess) throw new AppException(r.Message);
                    }
                    break;
                default:
                    throw new AppException($"设备类型[{realDevice.Type}]不支持直接下发物流任务，请检查");
            }
            return false;
        }

        /// <summary>
        /// 根据设备MachineId获取对应的数采设备
        /// </summary>
        public async Task<RealDevice> GetRealDeviceByMachineId(long? machineId)
        {
            var realDevices = Db.Set<Mds_RealDevice>().Where(x => x.MachineId == machineId).ToList();
            if (realDevices.Count == 0) throw new AppException("设备数据不存在");
            if (realDevices.Count > 1) throw new AppException("设备存在多个对应数采设备，请检查");

            var realDevice = devices.Where(x => x.Id == realDevices[0].RealDeviceId.ToString()).FirstOrDefault();
            return realDevice;
        }
    }

}
